package lnet

import (
	"container/list"
	"errors"
	"net"
	"sync"
	"sync/atomic"
)

var (
	SessionClosedError  = errors.New("Session Closed")
	SessionBlockedError = errors.New("Session Blocked")
)

var (
	globalSessionId uint64
)

type Session struct {
	net.Conn

	id        uint64
	codec     Codec
	manager   *Manager
	sendChan  chan interface{}
	recvMutex sync.Mutex
	sendMutex sync.RWMutex

	closeFlag      int32
	closeChan      chan int
	closeMutex     sync.Mutex
	closeCallbacks *list.List

	State atomic.Value
}

func NewSession(codec Codec, conn net.Conn, sendChanSize int) *Session {
	return newSession(nil, codec, conn, sendChanSize)
}

func newSession(manager *Manager, codec Codec, conn net.Conn, sendChanSize int) *Session {
	sess := &Session{
		Conn:           conn,
		codec:          codec,
		manager:        manager,
		closeChan:      make(chan int),
		closeCallbacks: list.New(),
		id:             atomic.AddUint64(&globalSessionId, 1),
	}
	if sendChanSize > 0 {
		sess.sendChan = make(chan interface{}, sendChanSize)
		go sess.sendLoop()
	}
	return sess
}

func (sess *Session) Id() uint64     { return sess.id }
func (sess *Session) IsClosed() bool { return atomic.LoadInt32(&sess.closeFlag) == 1 }
func (sess *Session) Codec() Codec   { return sess.codec }
func (sess *Session) String() string { return sess.RemoteAddr().String() }

func (sess *Session) Close() error {
	if atomic.CompareAndSwapInt32(&sess.closeFlag, 0, 1) {
		close(sess.closeChan)

		if sess.sendChan != nil {
			sess.sendMutex.Lock()
			close(sess.sendChan)
			if clear, ok := sess.codec.(ClearSendChan); ok {
				clear.ClearSendChan(sess.sendChan)
			}
			sess.sendMutex.Unlock()
		}

		err := sess.codec.Close()

		go func() {
			sess.invokeCloseCallbacks()
			if sess.manager != nil {
				sess.manager.delSession(sess)
			}
		}()
		return err
	}
	return SessionClosedError
}

func (sess *Session) Receive(msg interface{}) error {
	sess.recvMutex.Lock()
	defer sess.recvMutex.Unlock()

	err := sess.codec.Receive(msg)
	if err != nil {
		sess.Close()
	}
	return err
}

func (sess *Session) sendLoop() {
	defer sess.Close()
	for {
		select {
		case msg, ok := <-sess.sendChan:
			if !ok || sess.codec.Send(msg) != nil {
				return
			}
		case <-sess.closeChan:
			return
		}
	}
}

func (sess *Session) Send(msg interface{}) error {
	if sess.sendChan == nil {
		if sess.IsClosed() {
			return SessionClosedError
		}

		sess.sendMutex.Lock()
		defer sess.sendMutex.Unlock()

		err := sess.codec.Send(msg)
		if err != nil {
			sess.Close()
		}
		return err
	}

	sess.sendMutex.Lock()
	defer sess.sendMutex.Unlock()
	if sess.IsClosed() {
		return SessionClosedError
	}

	select {
	case sess.sendChan <- msg:
		return nil
	default:
		sess.Close()
		return SessionBlockedError
	}
}

type closeCallback struct {
	Handler interface{}
	Key     interface{}
	Func    func()
}

func (sess *Session) AddCloseCallback(handler, key interface{}, callback func()) {
	if sess.IsClosed() {
		return
	}

	sess.closeMutex.Lock()
	sess.closeCallbacks.PushBack(closeCallback{handler, key, callback})
	sess.closeMutex.Unlock()
}

func (sess *Session) RemoveCloseCallback(handler, key interface{}) {
	if sess.IsClosed() {
		return
	}

	sess.closeMutex.Lock()
	for i := sess.closeCallbacks.Front(); i != nil; i = i.Next() {
		if i.Value.(closeCallback).Handler == handler && i.Value.(closeCallback).Key == key {
			sess.closeCallbacks.Remove(i)
			break
		}
	}
	sess.closeMutex.Unlock()
}

func (sess *Session) invokeCloseCallbacks() {
	sess.closeMutex.Lock()
	for i := sess.closeCallbacks.Front(); i != nil; i = i.Next() {
		callback := i.Value.(closeCallback)
		callback.Func()
	}
	sess.closeMutex.Unlock()
}
